package dkbta.ontology.loading;

import static dkbta.ontology.elements.ElementType.STAT;
import static dkbta.ontology.elements.ElementType.SUBJECTSTAT;
import static dkbta.ontology.loading.OntologyParser.getXmlElement;
import static dkbta.ontology.loading.OntologyParser.parsePercentRange;
import static dkbta.ontology.loading.OntologyParser.parseValueConsdition;
import static dkbta.util.time.TimeUnit.DAY;
import static dkbta.util.time.TimeUnit.HOUR;
import static dkbta.util.time.TimeUnit.MONTH;
import static dkbta.util.time.TimeUnit.WEEK;

import java.util.EnumSet;
import java.util.Set;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import dkbta.ontology.Ontology;
import dkbta.ontology.elements.ElementType;
import dkbta.ontology.elements.subjectstat.SubjectStatDef;
import dkbta.ontology.loading.OntologyParser.ElementParser;
import dkbta.ontology.values.PercentValues;
import dkbta.ontology.values.ValueCondition;
import dkbta.util.Utils;
import dkbta.util.time.TimeUnit;

final class SubjectStatParser implements ElementParser{
	private final Set<ElementType> _invalidAbstractionElements = EnumSet.of(STAT,
		SUBJECTSTAT);

	private final Set<TimeUnit> _validTimeUnits = EnumSet.of(HOUR, DAY, WEEK, MONTH);

	private final Ontology _ontology;

	public SubjectStatParser(Ontology ontology){
		_ontology = ontology;
	}

	@Override
	public ElementType getElementType(){
		return ElementType.SUBJECTSTAT;
	}

	public void parseElement(Element subjectStat, String name, String componentType)
			throws OntologyException{

		Element afsElement = getXmlElement(subjectStat, "AbstractedFrom");
		if (afsElement == null){
			throw new OntologyException("No 'AbstractedFrom' element");
		}

		Element afElement = getAfElement(afsElement);

		// Parsing the value condition on the element
		ValueCondition valueCondition = parseValueConsdition(afElement, afElement
				.getTagName(), _invalidAbstractionElements, false);
		if (valueCondition == null){
			throw new OntologyException("A subject-statistical abstraction must have "
					+ "a value condition on an element from the ontology");
		}

		// Parsing the required percent range
		PercentValues percentRange;
		try{
			percentRange = parsePercentRange(subjectStat, true, null);
			if (percentRange == null){
				throw new OntologyException("Missing 'PercentRange' element");
			}
		}catch(OntologyException e){
			throw new OntologyException("Invalid percent range", e);
		}

		// Parsing the time frame
		Element timeFrameElement = getXmlElement(subjectStat, "TimeFrame");
		if (timeFrameElement == null){
			throw new OntologyException("No 'TimeFrame' element");
		}
		TimeUnit timeUnit = Utils.enumForName(TimeUnit.class, timeFrameElement
				.getAttribute("timeUnit"));
		if (!_validTimeUnits.contains(timeUnit)){
			throw new OntologyException(
					"Invalid time unit for subject-statistical abstractions: " + timeUnit);
		}

		int numOfTimeUnits;
		String numOfTimeUnitsStr = timeFrameElement.getAttribute("numOfTimeUnits");
		if (Utils.isEmpty(numOfTimeUnitsStr) || !Utils.isDigitsOnly(numOfTimeUnitsStr)
				|| (numOfTimeUnits = Integer.valueOf(numOfTimeUnitsStr)) < 1){
			throw new OntologyException("Invalid/missing 'numOfTimeUnits': "
					+ numOfTimeUnitsStr);
		}

		SubjectStatDef ssd = new SubjectStatDef(name, componentType, valueCondition,
				percentRange, timeUnit, numOfTimeUnits);
		_ontology.addSubjectStat(ssd);
	}

	private Element getAfElement(Element afsElement) throws OntologyException{
		NodeList childNodes = afsElement.getChildNodes();

		int elementNodeCount = 0;
		Element afElement = null;
		for (int i = 0; i < childNodes.getLength(); ++i){
			Node node = childNodes.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE){
				elementNodeCount++;
				afElement = (Element)node;
			}
		}

		if (elementNodeCount != 1){
			throw new OntologyException(
					"A subject0statistical abstraction can be abstracted from only one element and not "
							+ elementNodeCount);
		}
		return afElement;
	}
}